package QC.MessageBox.handler;

import QC.MessageBox.annotation.MethodLog;
import QC.MessageBox.annotation.RouteMapping;
import QC.MessageBox.beans.HandlerMapping;
import QC.MessageBox.common.PeriodicTime;
import com.fasterxml.jackson.core.JsonEncoding;
import io.netty.handler.codec.json.JsonObjectDecoder;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by Andaren on 2017/6/7.
 * Used for: 接口调度
 */
public class DispatherHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(DispatherHandler.class);

    private Map<String, HandlerMapping> REQUEST_METHOD_MAP = new HashMap<>();

    private Map<String, Object> handlerMap;

    private static String HANDLERS_PACKAGE;

    private Router router;

//    private MongoService mongoService;

    public DispatherHandler() {

    }

    public DispatherHandler(JsonObject config, Router router, Map<String, Object> handlerMap) {
        HANDLERS_PACKAGE = config.getString("dispatherHandlerPackage", "QC.MessageBox.handler");
        this.handlerMap = handlerMap;
        this.router = router;
//        this.mongoService = (MongoService) handlerMap.get("mongoService");
    }

    public void loadJarFileAndInit() {
        try {
            JarFile jarFile = null;
            // 获取项目根路径
            File dirFiles = new File(System.getProperty("user.dir"));
            List<File> jarFileList = new ArrayList<>();
            getProjectJarFileFromUserDir(dirFiles, jarFileList);
            boolean cannotFind = false;
            if (jarFileList.size() == 0) {
                cannotFind = true;
            } else {
                for (File jar : jarFileList) {
                    if (jar.getName().contains("MessageBox")
                            && jar.getName().endsWith("-fat.jar")) {
                        jarFile = new JarFile(jar);
                        break;
                    }
                }
                if (jarFile == null) {
                    cannotFind = true;
                }
            }
            if (cannotFind) {
                throw new RuntimeException("找不到主程序jar包文件");
            }
            List<Class<?>> clazzList = getClassInJarPackage(jarFile, HANDLERS_PACKAGE);
            for (Class clazz : clazzList) {
                HandlerBase handler = filterClassConstructor(clazz);
                initDispatherByClass(clazz, handler, router);
            }
            LOGGER.info("--------------------->>初始化路径映射完成<<----------------------");
            logHandlerMapping();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从项目根路径加载jar文件
     * @param dirFile
     * @param jarFileList
     */
    public void getProjectJarFileFromUserDir(File dirFile, List<File> jarFileList) {
        if (dirFile.isDirectory()) {
            for (File f : dirFile.listFiles()) {
                getProjectJarFileFromUserDir(f, jarFileList);
            }
        } else {
            if (dirFile.getName().endsWith(".jar")) {
                jarFileList.add(dirFile);
            } else {
                return;
            }
        }
    }

    /**
     * 打印请求映射
     */
    public void logHandlerMapping() {
        if (REQUEST_METHOD_MAP.isEmpty()) {
            LOGGER.info("-------------->Request-Mapping is empty<--------------");
        } else {
            Map<String, List<JsonObject>> mapping = new HashMap<>();
            for (String key : REQUEST_METHOD_MAP.keySet()) {
                JsonObject pathMapJson = new JsonObject();
                HandlerMapping handlerMapping = REQUEST_METHOD_MAP.get(key);
                String handlerName = handlerMapping.getHandler().getClass().getName();
                if (mapping.get(handlerName) == null) {
                    List<JsonObject> requestPathList = new ArrayList<>();
                    mapping.put(handlerName, requestPathList);
                } else {
                    //
                }
                for (HttpMethod m : handlerMapping.getMethodMap().keySet()) {
                    if (handlerMapping.getMethodMap().get(m) != null) {
                        pathMapJson.put(m.name(), key);
                    }
                }
                mapping.get(handlerName).add(pathMapJson);
            }
            LOGGER.info("[Request-Mapping\n{}]", Json.encodePrettily(mapping));
        }
    }

    /**
     * 获取jar包中指定包路径下的所有.Class文件
     * @param jarFile
     * @param packageName
     * @return
     * @throws Exception
     */
    public List<Class<?>> getClassInJarPackage(JarFile jarFile, String packageName) throws Exception {
        StringBuilder packageNameSb = new StringBuilder(packageName.replaceAll("\\.", "/"));
        packageNameSb.append("/");
        Objects.requireNonNull(jarFile);
        List<Class<?>> clazzList = new ArrayList<>();
        Enumeration<JarEntry> innerFiles =  jarFile.entries();
        JarEntry entry;
        while(innerFiles.hasMoreElements()) {
            entry = innerFiles.nextElement();
            if (entry.getName().endsWith(".class")) {
                String entryName = entry.getName();
                if (StringUtils.isNotEmpty(entryName)
                        && entryName.startsWith(packageNameSb.toString())
                        && !entryName.contains("$")) {
                    LOGGER.info(">>>>>>>>>>>>>>>find class=[{}]", entryName);
                    String clazzName = entryName.replaceAll("/", ".");
                    clazzName = clazzName.replaceAll(".class", "");
                    Class<?> clazz = Class.forName(clazzName);
                    RouteMapping routeMapping = clazz.getAnnotation(RouteMapping.class);
                    if (routeMapping != null) {
                        clazzList.add(clazz);
                    }
                }
            }
        }
        return clazzList;
    }

    /**
     * 查找注入服务类的构造器
     * @param clazz
     * @return
     */
    public HandlerBase filterClassConstructor (Class<?> clazz) {
        Constructor[]  constructors = clazz.getConstructors();
        for (Constructor cons : constructors) {
            int paramCount = cons.getParameterCount();
            if (paramCount == 1) {
                Class<?> typeClass = cons.getParameterTypes()[0];
                if ("java.util.Map".equals(typeClass.getName())) {
                    Parameter parameter = cons.getParameters()[0];
                    Type type = parameter.getParameterizedType();
                    if ("java.util.Map<java.lang.String, java.lang.Object>".equals(type.getTypeName())) {
                        try {
                            HandlerBase handler = (HandlerBase) cons.newInstance(handlerMap);
                            return handler;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 通过Class文件初始化方法映射
     * @param clazz
     * @param handler
     * @param router
     */
    public void initDispatherByClass(Class<?> clazz, HandlerBase handler, Router router) {
        try {
            RouteMapping routeMappingClassAnnotation = clazz.getAnnotation(RouteMapping.class);
            String classPath = routeMappingClassAnnotation.path();
            Method[] methods = clazz.getDeclaredMethods();
            for (Method m : methods) {
                RouteMapping methodRouteMapping = m.getAnnotation(RouteMapping.class);
                if (methodRouteMapping == null) {
                    continue;
                }
                String methodPath = methodRouteMapping.path();
                HttpMethod requestMethod = methodRouteMapping.method();
                String requestPath = classPath + methodPath;

                HandlerMapping handlerMapping = REQUEST_METHOD_MAP.get(requestPath);
                if (handlerMapping == null) {
                    EnumMap<HttpMethod, Method> methodMap = new EnumMap<>(HttpMethod.class);
                    handlerMapping = new HandlerMapping(requestPath, handler, methodMap);
                    REQUEST_METHOD_MAP.put(requestPath, handlerMapping);
                }

                Route newRoute = router.route(requestPath);
                if (requestMethod == HttpMethod.GET) {
                    newRoute.method(HttpMethod.GET);
                    handlerMapping.getMethodMap().put(HttpMethod.GET, m);
                } else if (requestMethod == HttpMethod.POST) {
                    newRoute.method(HttpMethod.POST);
                    handlerMapping.getMethodMap().put(HttpMethod.POST, m);
                }
                newRoute.handler(this::requestHandler);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 统一请求处理
     * @param context
     */
    public void requestHandler(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpMethod requestMethod = request.method();
        String requestPath = request.path();

        HandlerMapping handlerMapping = getMappedHandler(requestPath);
        Method m = handlerMapping.getHandlerMethod(requestMethod);
        MethodLog methodLog = m.getAnnotation(MethodLog.class);

        /**
         * 请求处理前
         * 1.记录请求信息
         */
        beforeInvoke(handlerMapping, context);
        /**
         * 1.调用处理方法
         * 2.记录异常
         * 3.异常报警（通过微信公众号报警给开发人员）
         */
        try {
            m.invoke(handlerMapping.getHandler(), context);
        } catch (Exception e) {
            LOGGER.error("[{}]处理失败，error=[{}]", methodLog==null?handlerMapping.getKeyPath():methodLog.value(), e.getMessage());
            e.printStackTrace();
        }
        /**
         * 请求处理后
         * 1.处理请求返回
         */
        afterInvoked(m, context);
    }

    /**
     * 根据请求路径中获取处理请求的Handler
     * @param requestPath
     * @return
     */
    public HandlerMapping getMappedHandler(String requestPath) {
        String[] requestPathSplit = requestPath.split("/");
        for (String key : REQUEST_METHOD_MAP.keySet()) {
            String[] keyPathSplit = key.split("/");
            if (requestPathSplit.length == keyPathSplit.length) {
                boolean isNotFound = false;
                for (int i = 0; i < requestPathSplit.length; i++) {
                    if (requestPathSplit[i].equals(keyPathSplit[i])) {
                        continue;
                    } else {
                        if (keyPathSplit[i].startsWith(":")) {
                            continue;
                        } else {
                            isNotFound = true;
                            break;
                        }
                    }
                }
                if (isNotFound) {
                    continue;
                } else {
                    HandlerMapping handlerMapping = REQUEST_METHOD_MAP.get(key);
                    return handlerMapping;
                }
            } else {
                continue;
            }
        }
        return null;
    }

    public void beforeInvoke(HandlerMapping handlerMapping, RoutingContext context) {
        Objects.requireNonNull(handlerMapping);
        Objects.requireNonNull(context);

        HttpServerRequest request = context.request();
        Method method = handlerMapping.getMethodMap().get(request.method());
        MethodLog methodLog = method.getAnnotation(MethodLog.class);

        JsonObject requestLogJson = new JsonObject();

        HttpMethod requestMethod = request.method();
        requestLogJson
                .put("_id", PeriodicTime.getCurrentTimeByyyyMMddHHmmssSSS())
                .put("requestDescription", methodLog.value())
                .put("matchedPath", handlerMapping.getKeyPath())
                .put("requestPath", request.path())
                .put("method", requestMethod.name());

        JsonObject paramsJson = new JsonObject();
        switch (requestMethod) {
            case GET:
                MultiMap params = request.params();
                Iterator<Map.Entry<String, String>> paramIterator = params.iterator();
                while(paramIterator.hasNext()) {
                    Map.Entry<String, String> entry = paramIterator.next();
                    paramsJson.put(entry.getKey(), entry.getValue());
                }
                break;
            case POST:
                try {
                    JsonObject postJson = context.getBodyAsJson();
                    paramsJson.put("postJson", postJson);
                } catch (DecodeException decodeExeption) {
                    String postString = context.getBodyAsString();
                    paramsJson.put("postString", postString);
                }
                /*if (request.isExpectMultipart()) {
                    // 文件上传时
                    paramsJson.put("file", "暂不处理文件上传请求");
                } else {

                }*/
                break;
            case OTHER:
                break;
        }
        requestLogJson.put("params", paramsJson);
        LOGGER.info("Before invoke check params ");
        LOGGER.info("get a request \n{}", requestLogJson.encodePrettily());
//        mongoService.insert("request_log", requestLogJson);
    }

    public void afterInvoked(Method method, RoutingContext context) {
        MethodLog methodLog = method.getAnnotation(MethodLog.class);
        HttpServerResponse response = context.response();
        if (response.ended()) {
            return;
        }
        response
                .endHandler((Void) -> {
                    LOGGER.info("[{}]请求返回完成", methodLog.value());
                }).exceptionHandler(throwable -> {
                    String errorMsg;
                    if (throwable.getCause() != null) {
                        errorMsg = throwable.getCause().getMessage();
                    } else {
                        errorMsg = throwable.getMessage();
                    }
                    LOGGER.info("[{}]请求返回失败error=[{}]", methodLog.value(), errorMsg);
                });
        LOGGER.info("After invoked handler ");
    }


    //----------------------------------------------------------------------------------------------------------------------
    /**
     *
     * @param file
     * @param currentPath
     * @param clazzNameList
     * @param firstTime
     */
    public static void findClass (File file, StringBuilder currentPath, List<JsonObject> clazzNameList, boolean firstTime) {
        /**
         * 如果是在本地tomcat目录下，.class文件在classes下，则用该方法
         * 如果是检索jar包下，则用其他方法
         *
         */
        URL url = ClassLoader.getSystemResource("");
        String i = url.getPath();
        File files = new File(i);

        if (file.isDirectory()) {
            if (!firstTime) {
                currentPath.append(file.getName()).append(".");
            }
            for (File f : file.listFiles()) {
                findClass(f, currentPath, clazzNameList, false);
            }
            backToForwardIndex(currentPath);
        } else if (file.isFile()) {
            String className = file.getName().replace(".class", "");
            JsonObject fileJson = new JsonObject()
                    .put("projectPath", currentPath.toString() + className)
                    .put("fileName", file.getName())
                    .put("filePath", file.getPath())
                    .put("absolutePath", file.getAbsolutePath());
            try {
                String canonicalPath = file.getCanonicalPath();
                fileJson.put("canonicalPath", canonicalPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
            clazzNameList.add(fileJson);
            System.out.println(fileJson.encodePrettily());
            System.out.println("-------------------------------------");
        }
    }

    public static void backToForwardIndex (StringBuilder currentPath) {
        currentPath.delete(currentPath.length() -1, currentPath.length());
        int index = currentPath.lastIndexOf(".");
        if (index == -1) {

        } else {
            currentPath.delete(index + 1, currentPath.length());
        }

    }
}
